package com.eolwral.osmonitor;

import java.io.File;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CpuStat {

	private static final String CPU_POSSIBLE = "/sys/devices/system/cpu/possible";
	private static final String CPUINFO_MAX = "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq";
	private static final String CPUINFO_MIN = "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_min_freq";
	private static final String CPUINFO_ONLINE = "/sys/devices/system/cpu/cpu%d/online";
	private static final String CPU_SCALING_CUR = "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq";
	private static final String CPU_SCALING_MAX = "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_max_freq";
	private static final String CPU_SCALING_MIN = "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_min_freq";
	private static final String CPU_SCALING_GOR = "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor";
	private static final String CPU_SCALING_GOVERNORS = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors";
	private static final String CPU_SCALING_FREQUENCIES = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies";
	
	public static int GetCpuCount()
	{
		String file = CommonUtil.ReadFile(CPU_POSSIBLE).trim();
		String lastCpuNumber = file.substring(file.indexOf('-') + 1);
		try {
			return Integer.parseInt(lastCpuNumber) + 1;
		}
		catch (NumberFormatException e) {
			return 1;
		}
	}
	
	public static boolean GetCpuOnline(int cpu)
	{
		try {
			String file = CommonUtil.ReadFile(String.format((Locale)null, CPUINFO_ONLINE, cpu)).trim();
			return Integer.parseInt(file) != 0;
		} catch (NumberFormatException e) {
			return false;
		}
	}
	
	public static int GetCpuMaxFreq(int cpu)
	{
		try {
			String file = CommonUtil.ReadFile(String.format((Locale)null, CPUINFO_MAX, cpu)).trim();
			return Integer.parseInt(file);
		} catch (NumberFormatException e) {
			return 0;
		}
	}
	
	public static int GetCpuMinFreq(int cpu)
	{
		try {
			String file = CommonUtil.ReadFile(String.format((Locale)null, CPUINFO_MIN, cpu)).trim();
			return Integer.parseInt(file);
		} catch (NumberFormatException e) {
			return 0;
		}
	}
	
	public static int GetCpuFreq(int cpu)
	{
		try {
			String file = CommonUtil.ReadFile(String.format((Locale)null, CPU_SCALING_CUR, cpu)).trim();
			return Integer.parseInt(file);
		} catch (NumberFormatException e) {
			return 0;
		}
	}
	
	public static int GetCpuMaxScalingFreq(int cpu)
	{
		try {
			String file = CommonUtil.ReadFile(String.format((Locale)null, CPU_SCALING_MAX, cpu)).trim();
			return Integer.parseInt(file);
		} catch (NumberFormatException e) {
			return 0;
		}
	}
	
	public static int GetCpuMinScalingFreq(int cpu)
	{
		try {
			String file = CommonUtil.ReadFile(String.format((Locale)null, CPU_SCALING_MIN, cpu)).trim();
			return Integer.parseInt(file);
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	public static int[] GetCpuScalingFreqList() 
	{
		String[] frequencies = CommonUtil.ReadFile(CPU_SCALING_FREQUENCIES).trim().split(" ");
		int[] result = new int[frequencies.length];

		for (int i = 0, j = frequencies.length; i < j; ++i) {
			result[i] = Integer.parseInt(frequencies[i]);
		}
		return result;
	}

	public static String[] GetCpuGovernorList() 
	{
		return CommonUtil.ReadFile(CPU_SCALING_GOVERNORS).trim().split(" ");
	}

	public static String GetCpuGovernor(int cpu)
	{
		return CommonUtil.ReadFile(String.format((Locale)null, CPU_SCALING_GOR, cpu)).trim();
	}
	
	// CPU Temperature.
	private static final String OMAP_TEMPERATURE = "/sys/class/hwmon/hwmon0/device/temp1_input";
	private static final String TEGRA3_TEMPERATURE = "/sys/kernel/debug/tegra_thermal/temp_tj";
	private static final String TEGRA3_SMBUS_TEMPERATURE = "/sys/kernel/debug/nct1008";
	
	/**
	 * Gets the temperature of the CPU, in Celsius.
	 * 
	 * @return The temperature of the CPU.
	 */
	public static float GetCpuTemperature() {
		// OMAP3430 temperature
		String temp = CommonUtil.ReadFile(OMAP_TEMPERATURE);
		if (temp.length() != 0) {
			return Integer.parseInt(temp);
		}

		// Tegra 3 SMBus temperature
		float floatTemp = Tegra3GetCpuTemperature();
		if (!Float.isNaN(floatTemp)) {
			return floatTemp;
		}
		
		return 0f;
	}
	
	// Tegra 3 specific statistics.
	private static final String CPU_TEGRA3_MAX_CORES = "/sys/kernel/debug/tegra_hotplug/max_cpus";
	private static final String CPU_TEGRA3_ACTIVE_GROUP = "/sys/kernel/cluster/active";
	
	public static boolean Tegra3IsTegra3() {
		return new File(TEGRA3_HOTPLUG_STATS).exists();
	}

	public static byte Tegra3GetEnabledCoreCount() {
		String raw = CommonUtil.ReadFileLine(CPU_TEGRA3_MAX_CORES);
		if (raw.length() == 0) {
			return -1;
		}
		
		return Byte.parseByte(raw);
	}

	public static boolean Tegra3IsLowPowerGroupActive() {
		return "LP".equals(Tegra3GetActiveCpuGroup());
	}
	
	public static String Tegra3GetActiveCpuGroup() {
		String result = CommonUtil.ReadFileLine(CPU_TEGRA3_ACTIVE_GROUP).trim();
		if (result.length() == 0) {
			return null;
		}
		
		return result;
	}

	private static final String TEGRA3_HOTPLUG_STATS = "/sys/kernel/debug/tegra_hotplug/stats";
	private static final Pattern TEGRA3_HOTPLUG_STAT_PATTERN = Pattern.compile(
			"^([^:]+?):((\\s+?(\\S+))+?)\\s*$", Pattern.MULTILINE);
	private static final Pattern TEGRA3_HOTPLUG_STAT_LINE_PATTERN = Pattern.compile(
			"(\\s+?(\\S+))+?");

	public static class Tegra3CpuStat {
		public Tegra3CpuStat(String name, int transitions, int activeTime) {
			this.Name = name;
			this.Transitions = transitions;
			this.ActiveTime = activeTime;
		}
		
		public String GetName() {
			return Name;
		}
		
		public void SetTransitions(int transitions) {
			Transitions = transitions;
		}
		
		public int GetTransitions() {
			return Transitions;
		}
		
		public void SetActiveTime(int activeTime) {
			ActiveTime = activeTime;
		}
		
		public int GetActiveTime() {
			return ActiveTime;
		}
		
		private String Name;
		private int Transitions;
		private int ActiveTime;
	}

	public static Tegra3CpuStat[] Tegra3GetHotplugStatistics() {
		String output = CommonUtil.ReadFile(TEGRA3_HOTPLUG_STATS);
		Matcher matcher = TEGRA3_HOTPLUG_STAT_PATTERN.matcher(output);
		ArrayList<Tegra3CpuStat> result = new ArrayList<Tegra3CpuStat>();
		while (matcher.find()) {
			String data = matcher.group(2);
			Matcher dataMatcher = TEGRA3_HOTPLUG_STAT_LINE_PATTERN.matcher(data);
			
			if (matcher.group(1).equals("cpu")) {
				while (dataMatcher.find()) {
					result.add(new Tegra3CpuStat(dataMatcher.group(2), 0, 0));
				}
			} else {
				for (int i = 0; dataMatcher.find(); ++i) {
					if (matcher.group(1).equals("transitions"))
						result.get(i).SetTransitions(Integer.parseInt(dataMatcher.group(2)));
					else if (matcher.group(1).equals("time plugged"))
						result.get(i).SetActiveTime(Integer.parseInt(dataMatcher.group(2)));
				}
			}
		}

		return result.toArray(new Tegra3CpuStat[0]);
	}
	
	private static float Tegra3GetCpuTemperature() {
		// Tegra 3 SMBus temperature
		float cputemp = Tegra3GetNct1008CpuTemperature();
		if (!Float.isNaN(cputemp)) {
			return cputemp;
		}
		
		// Tegra 3 diode temperature
		String result = CommonUtil.ReadFileLine(TEGRA3_TEMPERATURE);
		if (result.length() != 0) {
			int rawTemp = Integer.parseInt(result);
			return rawTemp / 1000f;
		}
		
		return Float.NaN;
	}
	
	
	/* defines from kernel: drivers/misc/nct1008.c */
	private static final int EXTENDED_RANGE_OFFSET = 64;
	private static final int STANDARD_RANGE_MAX	= 127;
	private static final int EXTENDED_RANGE_MAX	= (150 + EXTENDED_RANGE_OFFSET);
	private static short nct1008_value_to_temperature(boolean extended, short value) {
		return extended ? (short)(value - EXTENDED_RANGE_OFFSET) : (short)value;
	}
	
	private static final Pattern TEGRA3_NCT1008_STAT_LINE_PATTERN = Pattern.compile(
			"Addr = 0x[0-9a-fA-F]+ Reg 0x([0-9a-fA-F]+) Value 0x([0-9a-fA-F]+)");
	
	private static float Tegra3GetNct1008CpuTemperature() {
		String rawString = CommonUtil.ReadFile(TEGRA3_SMBUS_TEMPERATURE);
		int tempValueHigh = -1,
		    tempValueLow = -1;

		if (rawString.length() == 0) {
			return Float.NaN;
		}
		
		Matcher matcher = TEGRA3_NCT1008_STAT_LINE_PATTERN.matcher(rawString);
		while (matcher.find()) {
			int reg = Integer.parseInt(matcher.group(1), 16);
			int value = Integer.parseInt(matcher.group(2), 16);
			
			if (reg == 0x0) {
				tempValueLow = value;
			} else if (reg == 0x1) {
				tempValueHigh = value;
			}
		}

		if (tempValueHigh != -1 && tempValueLow != -1) {
			/* Based off nct1008_polling_func in nct1008.c */
			tempValueLow >>= 6;
			tempValueLow *= 25;
			tempValueHigh = nct1008_value_to_temperature(true /* for endeavouru: /arch/arm/mach-tegra/board-endeavoru-sensors.c */,
				(short)tempValueHigh);

			float result = tempValueLow;
			while (result > 1.0f)
				result /= 10.0f;
			result += tempValueHigh;
			return result;
		}

		return Float.NaN;
	}
}
